package org.fanjr.simplify.rest.utils;

import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;

import org.fanjr.simplify.rest.exceptions.SimplifyRuntimeException;

/**
 * @author fanjr@vip.qq.com
 * @since 20190222 10:29:28
 */
public class SimplifyClassUtil {

	private static Map<Class<?>, Supplier<?>> MAPPING = new ConcurrentHashMap<Class<?>, Supplier<?>>();

	static {
		MAPPING.put(Map.class, HashMap::new);
		MAPPING.put(List.class, ArrayList::new);
		MAPPING.put(Set.class, HashSet::new);
		MAPPING.put(Queue.class, LinkedList::new);
		MAPPING.put(Deque.class, LinkedList::new);
	}

	private SimplifyClassUtil() {
		// skip
	}

	@SuppressWarnings({ "unchecked" })
	public static <T> T newSimpleInstance(Class<T> type) {
		Supplier<?> supplier = MAPPING.get(type);
		if (null != supplier) {
			return (T) supplier.get();
		}
		try {
			T t = type.newInstance();
			MAPPING.put(type, () -> {
				try {
					return type.newInstance();
				} catch (Exception e) {// skip
					return null;
				}
			});
			return t;
		} catch (Exception e) {
			Constructor<T>[] constructors = (Constructor<T>[]) type.getConstructors();
			Constructor<T> constructor = constructors[0];
			constructor.setAccessible(true);
			if (constructor.getParameterCount() > 0) {
				List<Object> args = new ArrayList<>(constructor.getParameterCount());
				for (Class<?> parameterType : constructor.getParameterTypes()) {
					args.add(castNull(parameterType));
				}
				try {
					final Object[] arr = args.toArray();
					T t = constructor.newInstance(arr);
					MAPPING.put(type, () -> {
						try {
							return constructor.newInstance(arr);
						} catch (Exception e1) {// skip
							return null;
						}
					});
					return t;
				} catch (Exception e1) {
					if (e1 instanceof RuntimeException) {
						throw (RuntimeException) e1;
					} else {
						throw new SimplifyRuntimeException(e1);
					}
				}
			} else {
				try {
					T t = constructor.newInstance();
					MAPPING.put(type, () -> {
						try {
							return constructor.newInstance();
						} catch (Exception e1) {// skip
							return null;
						}
					});
					return t;
				} catch (Exception e1) {
					if (e1 instanceof RuntimeException) {
						throw (RuntimeException) e1;
					} else {
						throw new SimplifyRuntimeException(e1);
					}
				}
			}
		}
	}

	public static <T> void addDefaultInterfaceConstructor(Class<T> inter, Supplier<?> constructor) {
		MAPPING.put(inter, constructor);
	}

	private static Object castNull(Class<?> type) {
		if (type.isPrimitive()) {
			if (type == int.class) {
				return Integer.valueOf(0);
			} else if (type == long.class) {
				return Long.valueOf(0);
			} else if (type == short.class) {
				return Short.valueOf((short) 0);
			} else if (type == byte.class) {
				return Byte.valueOf((byte) 0);
			} else if (type == float.class) {
				return Float.valueOf(0);
			} else if (type == double.class) {
				return Double.valueOf(0);
			} else if (type == boolean.class) {
				return Boolean.FALSE;
			} else if (type == char.class) {
				return (char) 0;
			}
		} else if (type.isArray()) {
			return Array.newInstance(type.getComponentType(), 0);
		}
		return null;

	}

	public static <A extends Annotation> Set<Class<?>> findInterfaceForAnnotation(Class<?> type, Class<A> annotation) {
		Set<Class<?>> target = new HashSet<>();
		Class<?> sup = type.getSuperclass();
		if (null != sup && Object.class != sup) {
			target.addAll(findInterfaceForAnnotation(type.getSuperclass(), annotation));
		}

		if (type.isInterface()) {
			if (null != type.getAnnotation(annotation)) {
				target.add(type);
			}
		} else {
			for (Class<?> subInter : type.getInterfaces()) {
				target.addAll(findInterfaceForAnnotation(subInter, annotation));
			}
		}

		return target;
	}
}
